package com.example.wxtest.util;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class SignUtil {
    @Autowired
    RedisTemplate<String, String> redisTemplate;

    //    1.获取accessTokenUrl
    public  synchronized String getAccessToken() {

        Object redisToken = redisTemplate.opsForValue().get("accessToken");
        if (redisToken == null) {
            String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=" + "client_credential"
                    + "&appid=" + Configure.appId + "&secret=" + Configure.appsecret;
            // 获取accessToken
            String accessTokenBody = cn.hutool.http.HttpUtil.get(accessTokenUrl);

            log.info("获取accessToken{}", accessTokenBody);

            com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(accessTokenBody);
            String accessToken = null;
            try {
                accessToken = (String) jsonObject.get("access_token");
                log.info("accessToken:{}", accessToken);
            } catch (Exception e) {
                log.error("获取accessToken异常");
            }
            // 存储一个小时
            redisTemplate.opsForValue().set("accessToken", accessToken, 1, TimeUnit.HOURS);
            return accessToken;
        } else {
            return redisToken.toString();
        }
    }

    //    2.获取jsapiTicket
    public synchronized String getTicket(String accessToken) {

        Object jsapiTicket = redisTemplate.opsForValue().get("jsapiTicket");

        if (jsapiTicket == null) {
            String getTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken
                    + "&type=jsapi";

            // 获取getTicketResult
            String getTicketResult = cn.hutool.http.HttpUtil.get(getTicketUrl);
            JSONObject ticketResultObject = JSONObject.parseObject(getTicketResult);

            String ticket = null;
            try {
                ticket = (String) ticketResultObject.get("ticket");
                log.info("ticket:{}", ticket);
            } catch (Exception e) {
                log.error("获取ticket异常");
            }

            // 缓存中的accessToken可能不是最新的，导致获取ticket失败
            // 清除redis缓存的accessToken重新获取 不使用递归避免其他原因获取失败而重复调用
            if (ticket == null) {
                redisTemplate.delete("accessToken");
                return getTicket(accessToken);
            }
            return ticket;

        } else {
            return jsapiTicket.toString();
        }

    }

    private String encryptSHA(String signStr) {
        StringBuffer hexValue = new StringBuffer();
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA-1");
            byte[] byteArray = signStr.getBytes("UTF-8");
            byte[] md5Bytes = sha.digest(byteArray);
            for (int i = 0; i < md5Bytes.length; i++) {
                int val = ((int) md5Bytes[i]) & 0xff;
                if (val < 16) {
                    hexValue.append("0");
                }
                hexValue.append(Integer.toHexString(val));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }

        return hexValue.toString();
    }

    //    sha1签名算法
    public static String sha1(String data) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        //把字符串转为字节数组
        byte[] b = data.getBytes();
        //使用指定的字节来更新我们的摘要
        md.update(b);
        //获取密文  （完成摘要计算）
        byte[] b2 = md.digest();
        //获取计算的长度
        int len = b2.length;
        //16进制字符串
        String str = "0123456789abcdef";
        //把字符串转为字符串数组
        char[] ch = str.toCharArray();
        //创建一个40位长度的字节数组
        char[] chs = new char[len * 2];
        //循环20次
        for (int i = 0, k = 0; i < len; i++) {
            //获取摘要计算后的字节数组中的每个字节
            byte b3 = b2[i];
            // >>>:无符号右移
            // &:按位与
            //0xf:0-15的数字
            chs[k++] = ch[b3 >>> 4 & 0xf];
            chs[k++] = ch[b3 & 0xf];
        }
        //字符数组转为字符串
        return new String(chs);
    }
    public cn.hutool.json.JSONObject createJsapiTicket(String url) {
        String appId = "";
        String accessToken = getAccessToken();
        String ticket = getTicket(accessToken);
        long timestamp = System.currentTimeMillis() / 1000;
        String noncestr = "letsschool";

        String signStr = new StringBuilder("jsapi_ticket=")
                .append(ticket)
                .append("&noncestr=")
                .append(noncestr)
                .append("&timestamp=")
                .append(timestamp)
                .append("&url=")
                .append(url)
                .toString();

        log.info("signStr:{}", signStr);

        String signature = encryptSHA(signStr);
        cn.hutool.json.JSONObject returnJson = JSONUtil.createObj();
        returnJson.put("timestamp", timestamp);
        returnJson.put("ticket", ticket);
        returnJson.put("noncestr", noncestr);
        returnJson.put("signature", signature);
        returnJson.put("appId", appId);

        return returnJson;
    }

    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url   发送请求的URL
     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }

        } catch (Exception e) {
            System.out.println("发送GET请求出现异常！" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 发送json数据的post请求
     */

    public static String doPostToJson(String urlPath, String Json) {

        String result = "";
        BufferedReader reader = null;
        HttpURLConnection conn = null;
        try {
            trustAllHosts();
            URL url = new URL(urlPath);
            if (url.getProtocol().toLowerCase().equals("https")) {
                HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
                httpsConn.setHostnameVerifier(DO_NOT_VERIFY);
                conn = httpsConn;
            } else {
                conn = (HttpURLConnection) url.openConnection();
            }

            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Charset", "UTF-8");
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            // conn.setRequestProperty("accept","*/*");
            conn.setRequestProperty("accept", "application/json");
            if (Json != null) {
                byte[] writebytes = Json.getBytes();
                conn.setRequestProperty("Content-Length", String.valueOf(writebytes.length));
                OutputStream outwritestream = conn.getOutputStream();
                outwritestream.write(Json.getBytes());
                outwritestream.flush();
                outwritestream.close();
            }
            System.err.println("responseCode:" + conn.getResponseCode());
            if (conn.getResponseCode() == 200) {
                reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
                result = reader.readLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        public boolean verify(String arg0, SSLSession arg1) {
            return true;
        }
    };

    public static void trustAllHosts() {
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {

            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[]{};
            }

            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {

            }

            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {

            }
        }

        };

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url   发送请求的 URL
     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常！" + e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }
}
